package org.discala.scopes

import org.discala.Scope
import org.discala.util.logging.Logged

/**
 * The prototype scope uses providers that return a new instance on each invocation.
 * This is the simplest scope and mirrors the prototype scope in Spring Framework or
 * the Default scope in Google Guice.
 * <p>
 * This implementation of prototype scope just returns as a result the provider that
 * was supplied as a parameter with some logging added. Thus, the following two
 * constructs are functionally equivalent exept that the first outputs logging information
 * on each call:
 *
 * <code>
 * val connection = prototype(() => new Connection)
 * val connection = () => new Connection
 * </code>
 *
 * It is therefore the choice of the developer as to whether to use the prototype
 * scope or not. Our experience is that using this scope makes the developer's
 * intentions clearer and more explicit for developers who will be maintaining the
 * code and that the automated logging can be useful in tracking down problems.
 *
 * @author Chris Turner
 */
trait Prototype extends Scope with Logged {

  info("Initialising prototype scope")

  /**
   * Returns the same provider as provided in the provider parameter.
   *
   * @param provider The provider that creates new instances
   * @return The provider supplied as the parameter
   */
  def prototype[T](provider: () => T): () => T = {
    trace("Registering prototype provider for type: {}", determineType(provider))
    providerWithLogging(provider)
  }

  /**
   * Returns the same provider as provided in the provider parameter.
   *
   * @param qualifier Not used
   * @param provider The provider that creates new instances
   * @return The provider supplied as the parameter 
   */
  def prototype[T](qualifier:String, provider: () => T): () => T = {
    trace("Registering prototype provider for type: {}, qualified by: {}", determineType(provider), qualifier)
    providerWithLogging(provider)
  }

  private[this] def providerWithLogging[T](provider: () => T): () => T = {
    () => {
      debug("Creating prototype instance of type: {}", determineType(provider))
      provider()
    }
  }

  private[this] def determineType[T](provider: () => T) = provider.getClass.getMethod("apply").getReturnType.getName
}
